package com.terrace.core.mybatis.plugin.pagination;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.builder.annotation.ProviderSqlSource;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.RawSqlSource;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.terrace.core.mybatis.entity.Pagination;
import com.terrace.core.mybatis.entity.Signal;
import com.terrace.core.exception.MybatisPaginationException;
import com.terrace.core.mybatis.plugin.pagination.parser.AbstractParser;
import com.terrace.core.mybatis.plugin.pagination.parser.Parser;
import com.terrace.core.mybatis.plugin.pagination.sqlsource.CustomDynamicSqlSource;
import com.terrace.core.mybatis.plugin.pagination.sqlsource.CustomProviderSqlSource;
import com.terrace.core.mybatis.plugin.pagination.sqlsource.CustomRawSqlSource;
import com.terrace.core.mybatis.plugin.pagination.sqlsource.CustomStaticSqlSource;
import com.terrace.core.mybatis.plugin.pagination.sqlsource.PaginationSqlSource;

/**
 * 分页处理
 * @author jiangyg
 *
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class PaginationHandler {
	
	/**
	 * 日志
	 */
	private static final Logger logger = LoggerFactory.getLogger(PaginationHandler.class);
	
	/**
	 * 线程局部变量
	 */
	private static final ThreadLocal<Pagination> THREAD_LOCAL_PAGINATION = new ThreadLocal<Pagination>();
	
	/**
	 * MS缓存
	 */
	private static final Map<String, MappedStatement> MS_COUNT_MAP = new ConcurrentHashMap<String, MappedStatement>();

	/**
	 * SQL解析器
	 */
	private Parser parser;
	
	public PaginationHandler(Dialect dialect) {
		parser = AbstractParser.instanceParser(dialect);
	}
	
	public Object process(Invocation invocation) throws Throwable {
		try {
			Pagination pagination = PaginationHandler.getLocalPagination();
			
			Object[] args = invocation.getArgs();
			
			// 获取原始的ms
			MappedStatement ms = (MappedStatement) args[0];
			
			if (pagination == null) {
				throw new MybatisPaginationException("查询[" + ms.getId() + "]已转化为分页查询，但是未找到分页信息：");
			}
			
	        // 判断并处理为PageSqlSource
	        if (!isPaginationSqlSource(ms)) {
	            processMappedStatement(ms);
	        }
	        
	        // 处理排序字段，java属性->数据库列名
	        if (StringUtils.isNotBlank(pagination.getSort())) {
	        	handlePaginationSort(pagination, ms);
			}
	       
	        PaginationSqlSource.setParser(parser);
	        
	        /**
	         * 分页统计查询
	         */
	        if (pagination.isPagination()) {
	        	pagination.setSignal(Signal.COUNT);
	        	args[2] = RowBounds.DEFAULT;
	        	args[0] = MS_COUNT_MAP.get(ms.getId());				// 替换count的MS
	        	pagination.setTotal((Integer) ((List<Object>) invocation.proceed()).get(0));
			}
	        
	        /**
	         * 分页列表查询
	         */
	        pagination.setSignal(Signal.PAGINATION);
	        args[0] = ms;								// ms还原
	        BoundSql boundSql = ms.getBoundSql(args[1]);
	        args[1] = parser.setPageParameter(ms, args[1], boundSql, pagination);

	        return invocation.proceed();
		} catch (Exception e) {
			logger.error("Mybatis分页处理失败：", e);
		} finally {
			PaginationSqlSource.removeParser();
		}
		return null;
	}
	
	public static Pagination getLocalPagination() {
		return THREAD_LOCAL_PAGINATION.get();
	}
	
	public static void setLocalPagination(Pagination pagination) {
		THREAD_LOCAL_PAGINATION.set(pagination);
	}
	
	public static void removeLocalPagination() {
		THREAD_LOCAL_PAGINATION.remove();
	}
	
	/**
	 * 获取分页信号
	 * @return
	 */
	public static Signal getSignal() {
		return THREAD_LOCAL_PAGINATION.get().getSignal();
	}
	
	/**
	 * 处理排序字段，java属性->数据库列名
	 * @param pagination
	 * @param ms
	 */
	private void handlePaginationSort(Pagination pagination, MappedStatement ms) {
		Iterator<ResultMap> iterator = ms.getResultMaps().iterator();
		boolean success = false;
    	while (iterator.hasNext()) {
    		ResultMap resultMap = iterator.next();
    		List<ResultMapping> mappingList = resultMap.getResultMappings();
    		for (ResultMapping resultMapping : mappingList) {
				if (pagination.getSort().equals(resultMapping.getProperty())) {
					pagination.setSort(resultMapping.getColumn());
					success = true;
				}
			}
		}
    	if (!success) {
			logger.error("排序字段[" + pagination.getSort() + "]匹配失败，自动分页功能的排序字段必须在resultMap[结果集映射]有配置，否则不能根据Java属性名匹配到数据库的字段名");
			pagination.setSort(null);
		}
	}
	
    /**
     * 是否已经处理过
     *
     * @param ms
     * @return
     */
    private boolean isPaginationSqlSource(MappedStatement ms) {
        if (ms.getSqlSource() instanceof PaginationSqlSource) {
            return true;
        }
        return false;
    }
    
    /**
     * 修改SqlSource
     *
     * @param ms
     * @throws Throwable
     */
	private void processMappedStatement(MappedStatement ms) throws Throwable {
		SqlSource sqlSource = ms.getSqlSource();
		MetaObject msObject = SystemMetaObject.forObject(ms);
		SqlSource pageSqlSource;
		if (sqlSource instanceof StaticSqlSource) {
			pageSqlSource = new CustomStaticSqlSource((StaticSqlSource) sqlSource);
		} else if (sqlSource instanceof RawSqlSource) {
			pageSqlSource = new CustomRawSqlSource((RawSqlSource) sqlSource);
		} else if (sqlSource instanceof ProviderSqlSource) {
			pageSqlSource = new CustomProviderSqlSource((ProviderSqlSource) sqlSource);
		} else if (sqlSource instanceof DynamicSqlSource) {
			pageSqlSource = new CustomDynamicSqlSource((DynamicSqlSource) sqlSource);
		} else {
			throw new RuntimeException("无法处理该类型[" + sqlSource.getClass() + "]的SqlSource");
		}
		msObject.setValue("sqlSource", pageSqlSource);
		// 由于count查询需要修改返回值，因此这里要创建一个Count查询的MS
		MS_COUNT_MAP.put(ms.getId(), MSUtils.newCountMappedStatement(ms));
	}

}
